blob: babd9b3894b60a504ad784cfed61a888aff2da56 [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
41 -DarchetypeVersion=2.5-SNAPSHOT \
42 -DgroupId=com.google.gerrit \
43 -DartifactId=testPlugin
44----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
Edwin Kempinf7295742012-07-16 15:03:46 +0200135[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700136Reload Method
137~~~~~~~~~~~~~
138
139If a plugin holds an exclusive resource that must be released before
140loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200141acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700142to be `restart`. Otherwise the preferred method of `reload` will
143be used, as it enables the server to hot-patch an updated plugin
144with no down time.
145
146====
147 Gerrit-ReloadMode: restart
148====
149
150In either mode ('restart' or 'reload') any plugin or extension can
151be updated without restarting the Gerrit server. The difference is
152how Gerrit handles the upgrade:
153
154restart::
155 The old plugin is completely stopped. All registrations of SSH
156 commands and HTTP servlets are removed. All registrations of any
157 extension points are removed. All registered LifecycleListeners
158 have their `stop()` method invoked in reverse order. The new
159 plugin is started, and registrations are made from the new
160 plugin. There is a brief window where neither the old nor the
161 new plugin is connected to the server. This means SSH commands
162 and HTTP servlets will return not found errors, and the plugin
163 will not be notified of events that occurred during the restart.
164
165reload::
166 The new plugin is started. Its LifecycleListeners are permitted
167 to perform their `start()` methods. All SSH and HTTP registrations
168 are atomically swapped out from the old plugin to the new plugin,
169 ensuring the server never returns a not found error. All extension
170 point listeners are atomically swapped out from the old plugin to
171 the new plugin, ensuring no events are missed (however some events
172 may still route to the old plugin if the swap wasn't complete yet).
173 The old plugin is stopped.
174
Edwin Kempinf7295742012-07-16 15:03:46 +0200175To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
176command can be used.
177
Luca Milanesio737285d2012-09-25 14:26:43 +0100178[[init_step]]
179Init step
180~~~~~~~~~
181
182Plugins can contribute their own "init step" during the Gerrit init
183wizard. This is useful for guiding the Gerrit administrator through
184the settings needed by the plugin to work propertly.
185
186For instance plugins to integrate Jira issues to Gerrit changes may
187contribute their own "init step" to allow configuring the Jira URL,
188credentials and possibly verify connectivity to validate them.
189
190====
191 Gerrit-InitStep: tld.example.project.MyInitStep
192====
193
194MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900195and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100196and accessing / changing configuration settings using Section.Factory.
197
198In addition to the standard Gerrit init injections, plugins receive
199the @PluginName String injection containing their own plugin name.
200
201Bear in mind that the Plugin's InitStep class will be loaded but
202the standard Gerrit runtime environment is not available and the plugin's
203own Guice modules were not initialized.
204This means the InitStep for a plugin is not executed in the same way that
205the plugin executes within the server, and may mean a plugin author cannot
206trivially reuse runtime code during init.
207
208For instance a plugin that wants to verify connectivity may need to statically
209call the constructor of their connection class, passing in values obtained
210from the Section.Factory rather than from an injected Config object.
211
212Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
213the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
214and before the DB Schema initialization or upgrade.
215Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
216objects injected at startup.
217
David Pursehouse68153d72013-09-04 10:09:17 +0900218[source,java]
219----
220public class MyInitStep implements InitStep {
221 private final ConsoleUI ui;
222 private final Section.Factory sections;
223 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100224
David Pursehouse68153d72013-09-04 10:09:17 +0900225 @Inject
226 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
227 this.ui = ui;
228 this.sections = sections;
229 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100230 }
David Pursehouse68153d72013-09-04 10:09:17 +0900231
232 @Override
233 public void run() throws Exception {
234 ui.header("\nMy plugin");
235
236 Section mySection = getSection("myplugin", null);
237 mySection.string("Link name", "linkname", "MyLink");
238 }
239}
240----
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
Edwin Kempinf5a77332012-07-18 11:17:53 +0200242[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700243Classpath
244---------
245
246Each plugin is loaded into its own ClassLoader, isolating plugins
247from each other. A plugin or extension inherits the Java runtime
248and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
249from the hosting server.
250
251Plugins are loaded from a single JAR file. If a plugin needs
252additional libraries, it must include those dependencies within
253its own JAR. Plugins built using Maven may be able to use the
254link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
255to package additional dependencies. Relocating (or renaming) classes
256should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700257
Edwin Kempin98202662013-09-18 16:03:03 +0200258[[events]]
259Listening to Events
260-------------------
261
262Certain operations in Gerrit trigger events. Plugins may receive
263notifications of these events by implementing the corresponding
264listeners.
265
266* `com.google.gerrit.extensions.events.LifecycleListener`:
267+
268Gerrit server startup and shutdown
269
270* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
271+
272Project creation
273
274* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
275+
276Project deletion
277
Edwin Kempinf5a77332012-07-18 11:17:53 +0200278[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700279SSH Commands
280------------
281
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700282Plugins may provide commands that can be accessed through the SSH
283interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700284
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700285Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700286
David Pursehouse68153d72013-09-04 10:09:17 +0900287[source,java]
288----
289import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700290
David Pursehouse68153d72013-09-04 10:09:17 +0900291class PrintHello extends SshCommand {
292 protected abstract void run() {
293 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700294 }
David Pursehouse68153d72013-09-04 10:09:17 +0900295}
296----
Nasser Grainawie033b262012-05-09 17:54:21 -0700297
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700298If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200299use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700300
David Pursehouse68153d72013-09-04 10:09:17 +0900301[source,java]
302----
303import com.google.gerrit.extensions.annotations.Export;
304import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700305
David Pursehouse68153d72013-09-04 10:09:17 +0900306@Export("print")
307class PrintHello extends SshCommand {
308 protected abstract void run() {
309 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700310 }
David Pursehouse68153d72013-09-04 10:09:17 +0900311}
312----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700313
314If explicit registration is being used, a Guice module must be
315supplied to register the SSH command and declared in the manifest
316with the `Gerrit-SshModule` attribute:
317
David Pursehouse68153d72013-09-04 10:09:17 +0900318[source,java]
319----
320import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700321
David Pursehouse68153d72013-09-04 10:09:17 +0900322class MyCommands extends PluginCommandModule {
323 protected void configureCommands() {
324 command("print").to(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700325 }
David Pursehouse68153d72013-09-04 10:09:17 +0900326}
327----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700328
329For a plugin installed as name `helloworld`, the command implemented
330by PrintHello class will be available to users as:
331
332----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600333$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700334----
335
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200336[[configuration]]
337Configuration
338-------------
339
340In Gerrit, global configuration is stored in the `gerrit.config` file.
341If a plugin needs global configuration, this configuration should be
342stored in a `plugin` subsection in the `gerrit.config` file.
343
344To avoid conflicts with other plugins, it is recommended that plugins
345only use the `plugin` subsection with their own name. For example the
346`helloworld` plugin should store its configuration in the
347`plugin.helloworld` subsection:
348
349----
350[plugin "helloworld"]
351 language = Latin
352----
353
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200354Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200355plugin can easily access its configuration and there is no need for a
356plugin to parse the `gerrit.config` file on its own:
357
358[source,java]
359----
360 @Inject
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200361 private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200362
363 ...
364
365 String language = cfg.get("helloworld")
366 .getString("language", "English");
367----
368
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200369[[project-specific-configuration]]
370Project Specific Configuration
371------------------------------
372
373In Gerrit, project specific configuration is stored in the project's
374`project.config` file on the `refs/meta/config` branch. If a plugin
375needs configuration on project level (e.g. to enable its functionality
376only for certain projects), this configuration should be stored in a
377`plugin` subsection in the project's `project.config` file.
378
379To avoid conflicts with other plugins, it is recommended that plugins
380only use the `plugin` subsection with their own name. For example the
381`helloworld` plugin should store its configuration in the
382`plugin.helloworld` subsection:
383
384----
385 [plugin "helloworld"]
386 enabled = true
387----
388
389Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
390plugin can easily access its project specific configuration and there
391is no need for a plugin to parse the `project.config` file on its own:
392
393[source,java]
394----
395 @Inject
396 private com.google.gerrit.server.config.PluginConfigFactory cfg;
397
398 ...
399
400 boolean enabled = cfg.get(project, "helloworld")
401 .getBoolean("enabled", false);
402----
403
404Project owners can edit the project configuration by fetching the
405`refs/meta/config` branch, editing the `project.config` file and
406pushing the commit back.
407
David Ostrovsky7066cc02013-06-15 14:46:23 +0200408[[capabilities]]
409Plugin Owned Capabilities
410-------------------------
411
412Plugins may provide their own capabilities and restrict usage of SSH
413commands to the users who are granted those capabilities.
414
415Plugins define the capabilities by overriding the `CapabilityDefinition`
416abstract class:
417
David Pursehouse68153d72013-09-04 10:09:17 +0900418[source,java]
419----
420public class PrintHelloCapability extends CapabilityDefinition {
421 @Override
422 public String getDescription() {
423 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200424 }
David Pursehouse68153d72013-09-04 10:09:17 +0900425}
426----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200427
David Ostrovskyf86bae52013-09-01 09:10:39 +0200428If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200429use auto-registration by providing an `@Export` annotation:
430
David Pursehouse68153d72013-09-04 10:09:17 +0900431[source,java]
432----
433@Export("printHello")
434public class PrintHelloCapability extends CapabilityDefinition {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200435 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900436}
437----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200438
439Otherwise the capability must be bound in a plugin module:
440
David Pursehouse68153d72013-09-04 10:09:17 +0900441[source,java]
442----
443public class HelloWorldModule extends AbstractModule {
444 @Override
445 protected void configure() {
446 bind(CapabilityDefinition.class)
447 .annotatedWith(Exports.named("printHello"))
448 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200449 }
David Pursehouse68153d72013-09-04 10:09:17 +0900450}
451----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200452
453With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200454usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200455this capability in the usual way, using the `RequiresCapability` annotation:
456
David Pursehouse68153d72013-09-04 10:09:17 +0900457[source,java]
458----
459@RequiresCapability("printHello")
460@CommandMetaData(name="print", description="Print greeting in different languages")
461public final class PrintHelloWorldCommand extends SshCommand {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200462 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900463}
464----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200465
David Ostrovskyf86bae52013-09-01 09:10:39 +0200466Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200467
David Pursehouse68153d72013-09-04 10:09:17 +0900468[source,java]
469----
470@RequiresCapability("printHello")
471public class SayHelloAction extends UiAction<RevisionResource>
472 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200473 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900474}
475----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200476
477Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900478capabilities and core capabilities. Per default the scope of the
479`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
480
David Ostrovsky7066cc02013-06-15 14:46:23 +0200481* when `@RequiresCapability` is used within a plugin the scope of the
482capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900483
David Ostrovsky7066cc02013-06-15 14:46:23 +0200484* If `@RequiresCapability` is used within the core Gerrit Code Review server
485(and thus is outside of a plugin) the scope is the core server and will use
486the `GlobalCapability` known to Gerrit Code Review server.
487
488If a plugin needs to use a core capability name (e.g. "administrateServer")
489this can be specified by setting `scope = CapabilityScope.CORE`:
490
David Pursehouse68153d72013-09-04 10:09:17 +0900491[source,java]
492----
493@RequiresCapability(value = "administrateServer", scope =
494 CapabilityScope.CORE)
David Ostrovsky7066cc02013-06-15 14:46:23 +0200495 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900496----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200497
David Ostrovskyf86bae52013-09-01 09:10:39 +0200498[[ui_extension]]
499UI Extension
500------------
501
502Plugins can contribute their own UI commands on core Gerrit pages.
503This is useful for workflow customization or exposing plugin functionality
504through the UI in addition to SSH commands and the REST API.
505
506For instance a plugin to integrate Jira with Gerrit changes may contribute its
507own "File bug" button to allow filing a bug from the change page or plugins to
508integrate continuous integration systems may contribute a "Schedule" button to
509allow a CI build to be scheduled manually from the patch set panel.
510
511Two different places on core Gerrit pages are currently supported:
512
513* Change screen
514* Project info screen
515
516Plugins contribute UI actions by implementing the `UiAction` interface:
517
David Pursehouse68153d72013-09-04 10:09:17 +0900518[source,java]
519----
520@RequiresCapability("printHello")
521class HelloWorldAction implements UiAction<RevisionResource>,
522 RestModifyView<RevisionResource, HelloWorldAction.Input> {
523 static class Input {
524 boolean french;
525 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200526 }
David Pursehouse68153d72013-09-04 10:09:17 +0900527
528 private Provider<CurrentUser> user;
529
530 @Inject
531 HelloWorldAction(Provider<CurrentUser> user) {
532 this.user = user;
533 }
534
535 @Override
536 public String apply(RevisionResource rev, Input input) {
537 final String greeting = input.french
538 ? "Bonjour"
539 : "Hello";
540 return String.format("%s %s from change %s, patch set %d!",
541 greeting,
542 Strings.isNullOrEmpty(input.message)
543 ? Objects.firstNonNull(user.get().getUserName(), "world")
544 : input.message,
545 rev.getChange().getId().toString(),
546 rev.getPatchSet().getPatchSetId());
547 }
548
549 @Override
550 public Description getDescription(
551 RevisionResource resource) {
552 return new Description()
553 .setLabel("Say hello")
554 .setTitle("Say hello in different languages");
555 }
556}
557----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200558
559`UiAction` must be bound in a plugin module:
560
David Pursehouse68153d72013-09-04 10:09:17 +0900561[source,java]
562----
563public class Module extends AbstractModule {
564 @Override
565 protected void configure() {
566 install(new RestApiModule() {
567 @Override
568 protected void configure() {
569 post(REVISION_KIND, "say-hello")
570 .to(HelloWorldAction.class);
571 }
572 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200573 }
David Pursehouse68153d72013-09-04 10:09:17 +0900574}
575----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200576
577The module above must be declared in pom.xml for Maven driven plugins:
578
David Pursehouse68153d72013-09-04 10:09:17 +0900579[source,xml]
580----
581<manifestEntries>
582 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
583</manifestEntries>
584----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200585
586or in the BUCK configuration file for Buck driven plugins:
587
David Pursehouse68153d72013-09-04 10:09:17 +0900588[source,python]
589----
590manifest_entries = [
591 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
592]
593----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200594
595In some use cases more user input must be gathered, for that `UiAction` can be
596combined with the JavaScript API. This would display a small popup near the
597activation button to gather additional input from the user. The JS file is
598typically put in the `static` folder within the plugin's directory:
599
David Pursehouse68153d72013-09-04 10:09:17 +0900600[source,javascript]
601----
602Gerrit.install(function(self) {
603 function onSayHello(c) {
604 var f = c.textfield();
605 var t = c.checkbox();
606 var b = c.button('Say hello', {onclick: function(){
607 c.call(
608 {message: f.value, french: t.checked},
609 function(r) {
610 c.hide();
611 window.alert(r);
612 c.refresh();
613 });
614 }});
615 c.popup(c.div(
616 c.prependLabel('Greeting message', f),
617 c.br(),
618 c.label(t, 'french'),
619 c.br(),
620 b));
621 f.focus();
622 }
623 self.onAction('revision', 'say-hello', onSayHello);
624});
625----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200626
627The JS module must be exposed as a `WebUiPlugin` and bound as
628an HTTP Module:
629
David Pursehouse68153d72013-09-04 10:09:17 +0900630[source,java]
631----
632public class HttpModule extends HttpPluginModule {
633 @Override
634 protected void configureServlets() {
635 DynamicSet.bind(binder(), WebUiPlugin.class)
636 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200637 }
David Pursehouse68153d72013-09-04 10:09:17 +0900638}
639----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200640
641The HTTP module above must be declared in pom.xml for Maven driven plugins:
642
David Pursehouse68153d72013-09-04 10:09:17 +0900643[source,xml]
644----
645<manifestEntries>
646 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
647</manifestEntries>
648----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200649
650or in the BUCK configuration file for Buck driven plugins
651
David Pursehouse68153d72013-09-04 10:09:17 +0900652[source,python]
653----
654manifest_entries = [
655 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
656]
657----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200658
659If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
660capability check is done during the `UiAction` gathering, so the plugin author
661doesn't have to set `UiAction.Description.setVisible()` explicitly in this
662case.
663
664The following prerequisities must be met, to satisfy the capability check:
665
666* user is authenticated
667* user is a member of the Administrators group, or
668* user is a member of a group which has the required capability
669
670The `apply` method is called when the button is clicked. If `UiAction` is
671combined with JavaScript API (its own JavaScript function is provided),
672then a popup dialog is normally opened to gather additional user input.
673A new button is placed on the popup dialog to actually send the request.
674
675Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
676can be accessed from any REST client, i. e.:
677
678====
679 curl -X POST -H "Content-Type: application/json" \
680 -d '{message: "François", french: true}' \
681 --digest --user joe:secret \
682 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
683 "Bonjour François from change 1, patch set 1!"
684====
685
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200686Special case is to bind an endpoint without view name. This is
687particularly useful for DELETE requests:
688
689[source,java]
690----
691public class Module extends AbstractModule {
692 @Override
693 protected void configure() {
694 install(new RestApiModule() {
695 @Override
696 protected void configure() {
697 delete(PROJECT_KIND)
698 .to(DeleteProject.class);
699 }
700 });
701 }
702}
703----
704
705For an `UiAction` bound this way JS API function can be provided.
706Currently only one restriction exists: per plugin only one UiAction
707can be bound per resource without view name. To define a JS function
708for the `UiAction`, "/" must be used as the name:
709
710[source,javascript]
711----
712Gerrit.install(function(self) {
713 function onDeleteProject(c) {
714 [...]
715 }
716 self.onAction('project', '/', onDeleteProject);
717});
718----
719
Edwin Kempinf5a77332012-07-18 11:17:53 +0200720[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700721HTTP Servlets
722-------------
723
724Plugins or extensions may register additional HTTP servlets, and
725wrap them with HTTP filters.
726
727Servlets may use auto-registration to declare the URL they handle:
728
David Pursehouse68153d72013-09-04 10:09:17 +0900729[source,java]
730----
731import com.google.gerrit.extensions.annotations.Export;
732import com.google.inject.Singleton;
733import javax.servlet.http.HttpServlet;
734import javax.servlet.http.HttpServletRequest;
735import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700736
David Pursehouse68153d72013-09-04 10:09:17 +0900737@Export("/print")
738@Singleton
739class HelloServlet extends HttpServlet {
740 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
741 res.setContentType("text/plain");
742 res.setCharacterEncoding("UTF-8");
743 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700744 }
David Pursehouse68153d72013-09-04 10:09:17 +0900745}
746----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700747
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200748The auto registration only works for standard servlet mappings like
749`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
750to register the HTTP servlets and declare it explicitly in the manifest
751with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700752
David Pursehouse68153d72013-09-04 10:09:17 +0900753[source,java]
754----
755import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700756
David Pursehouse68153d72013-09-04 10:09:17 +0900757class MyWebUrls extends ServletModule {
758 protected void configureServlets() {
759 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700760 }
David Pursehouse68153d72013-09-04 10:09:17 +0900761}
762----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700763
764For a plugin installed as name `helloworld`, the servlet implemented
765by HelloServlet class will be available to users as:
766
767----
768$ curl http://review.example.com/plugins/helloworld/print
769----
Nasser Grainawie033b262012-05-09 17:54:21 -0700770
Edwin Kempinf5a77332012-07-18 11:17:53 +0200771[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200772Data Directory
773--------------
774
775Plugins can request a data directory with a `@PluginData` File
776dependency. A data directory will be created automatically by the
777server in `$site_path/data/$plugin_name` and passed to the plugin.
778
779Plugins can use this to store any data they want.
780
David Pursehouse68153d72013-09-04 10:09:17 +0900781[source,java]
782----
783@Inject
784MyType(@PluginData java.io.File myDir) {
785 new FileInputStream(new File(myDir, "my.config"));
786}
787----
Edwin Kempin41f63912012-07-17 12:33:55 +0200788
Edwin Kempinf5a77332012-07-18 11:17:53 +0200789[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700790Documentation
791-------------
792
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700793If a plugin does not register a filter or servlet to handle URLs
794`/Documentation/*` or `/static/*`, the core Gerrit server will
795automatically export these resources over HTTP from the plugin JAR.
796
David Pursehouse6853b5a2013-07-10 11:38:03 +0900797Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -0400798available as `/plugins/helloworld/static/resource`. This prefix is
799configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700800
David Pursehouse6853b5a2013-07-10 11:38:03 +0900801Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -0400802will be available as `/plugins/helloworld/Documentation/resource`. This
803prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
804attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700805
806Documentation may be written in
807link:http://daringfireball.net/projects/markdown/[Markdown] style
808if the file name ends with `.md`. Gerrit will automatically convert
809Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700810
Edwin Kempinf5a77332012-07-18 11:17:53 +0200811[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200812Within the Markdown documentation files macros can be used that allow
813to write documentation with reasonably accurate examples that adjust
814automatically based on the installation.
815
816The following macros are supported:
817
818[width="40%",options="header"]
819|===================================================
820|Macro | Replacement
821|@PLUGIN@ | name of the plugin
822|@URL@ | Gerrit Web URL
823|@SSH_HOST@ | SSH Host
824|@SSH_PORT@ | SSH Port
825|===================================================
826
827The macros will be replaced when the documentation files are rendered
828from Markdown to HTML.
829
830Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
831even if there is an expansion for `KEEP` in the future.
832
Edwin Kempinf5a77332012-07-18 11:17:53 +0200833[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700834Automatic Index
835~~~~~~~~~~~~~~~
836
837If a plugin does not handle its `/` URL itself, Gerrit will
838redirect clients to the plugin's `/Documentation/index.html`.
839Requests for `/Documentation/` (bare directory) will also redirect
840to `/Documentation/index.html`.
841
842If neither resource `Documentation/index.html` or
843`Documentation/index.md` exists in the plugin JAR, Gerrit will
844automatically generate an index page for the plugin's documentation
845tree by scanning every `*.md` and `*.html` file in the Documentation/
846directory.
847
848For any discovered Markdown (`*.md`) file, Gerrit will parse the
849header of the file and extract the first level one title. This
850title text will be used as display text for a link to the HTML
851version of the page.
852
853For any discovered HTML (`*.html`) file, Gerrit will use the name
854of the file, minus the `*.html` extension, as the link text. Any
855hyphens in the file name will be replaced with spaces.
856
David Pursehouse6853b5a2013-07-10 11:38:03 +0900857If a discovered file is named `about.md` or `about.html`, its
858content will be inserted in an 'About' section at the top of the
859auto-generated index page. If both `about.md` and `about.html`
860exist, only the first discovered file will be used.
861
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700862If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +0900863into a 'Commands' section of the generated index page.
864
David Pursehousefe529152013-08-14 16:35:06 +0900865If a discovered file name beings with `servlet-` it will be clustered
866into a 'Servlets' section of the generated index page.
867
868If a discovered file name beings with `rest-api-` it will be clustered
869into a 'REST APIs' section of the generated index page.
870
David Pursehouse6853b5a2013-07-10 11:38:03 +0900871All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700872
873Some optional information from the manifest is extracted and
874displayed as part of the index page, if present in the manifest:
875
876[width="40%",options="header"]
877|===================================================
878|Field | Source Attribute
879|Name | Implementation-Title
880|Vendor | Implementation-Vendor
881|Version | Implementation-Version
882|URL | Implementation-URL
883|API Version | Gerrit-ApiVersion
884|===================================================
885
Edwin Kempinf5a77332012-07-18 11:17:53 +0200886[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700887Deployment
888----------
889
Edwin Kempinf7295742012-07-16 15:03:46 +0200890Compiled plugins and extensions can be deployed to a running Gerrit
891server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700892
893Plugins can also be copied directly into the server's
894directory at `$site_path/plugins/$name.jar`. The name of
895the JAR file, minus the `.jar` extension, will be used as the
896plugin name. Unless disabled, servers periodically scan this
897directory for updated plugins. The time can be adjusted by
898link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700899
Edwin Kempinf7295742012-07-16 15:03:46 +0200900For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
901command can be used.
902
Brad Larsond5e87c32012-07-11 12:18:49 -0500903Disabled plugins can be re-enabled using the
904link:cmd-plugin-enable.html[plugin enable] command.
905
David Ostrovskyf86bae52013-09-01 09:10:39 +0200906SEE ALSO
907--------
908
909* link:js-api.html[JavaScript API]
910* link:dev-rest-api.html[REST API Developers' Notes]
911
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700912GERRIT
913------
914Part of link:index.html[Gerrit Code Review]